from livscm import types as lst
import livscm.utils as utils
import sys

module_template = '''
//
// Module : ${module}$
//

#include "livscm.h"
'''

util_template = """
${
def get_class_name(name):
	sname = name.split('.')
	result = ''
	l = len(sname)
	if l > 1:
		for n in sname[:-1]:
			if n == module or n == 'types':
				continue
			result += n + '::'
	result += sname[-1][0].upper() + sname[-1][1:]
	return result
def emit_class_name():
	emit(get_class_name(name))

def emit_tail_comment(DI, comment):
	lines = comment.split('\\n')
	cnt = 0
	for line in lines:
		if cnt == 0:
			emit(' // ' + line)
		else:
			emit(DI + '// ' + line)
		cnt += 1

def emit_col_tail_comment(comment, start_col = None):
	lines = comment.split('\\n')
	cnt = 0
	for line in lines:
		if cnt == 0:
			emit_col(' // ' + line, start_col)
		else:
			emit_row_start()
			emit_col(' // ' + line, start_col)
			emit_row_end()
		cnt += 1

def emit_derivation():
	if bases: 
		emit(' : ')
		l = len(bases)
		c = 0
		for base in bases: 
			bmn = base.__module__
			if bmn != module:
				bmn = bmn + '.'
			else:
				bmn = ''
			emit('public ' + get_class_name(bmn + base.__name__))
			c += 1
			if c < l:
				emit(', ')
def get_tab_char(count):
	if setting.tab_to_space:
		return ' ' * setting.tab_size * count
	else:
		return '\t' * count
def emit_comment(comment):
	lines = comment.split('\\n')
	for line in lines:
		print(DI + '// ' + line)
def emit_table_start():
	global table_rows
	table_rows = []
def emit_row_start():
	global table_rows, table_row, table_col
	table_row = {}
	table_col = 0
	for i in range(20):
		table_row[i] = ''
	table_rows.append(table_row)
def emit_col(msg, start_col = None):
	global table_col
	if start_col:
		table_col = start_col
	assert(table_col < 20)
	global table_row
	table_row[table_col] = table_row[table_col] + msg
	table_col += 1
	return table_col
def get_col_len(col = None):
	global table_row, table_col
	if not col:
		col = table_col
	return len(table_row[col])
def emit_row_end():
	pass
def emit_table_end():
	global table_rows
	col_space = {}
	for row in table_rows:
		for col in row.keys():
			t = row[col]
			if not( col in col_space ):
				col_space[col] = len(t)
			else:
				col_space[col] = max(col_space[col], len(t))
	for row in table_rows:
		for col in row.keys():
			t = row[col]
			s = col_space[col] - len(t)
			emit(t + (' ' * s))
		emit('\\n')
def emit_enum(name, DI, o):
	if o.comment:
		emit_comment(o.comment)
	emit(DI + 'enum ' + name + '\\n')
	emit(DI + '{\\n')
	cnt = 1
	l = len(o.items)
	keys = o.items.keys()
	keys.sort(key = lambda name: o.items[name].__instance_id__)
	emit_table_start()
	for iname in keys:
		emit_row_start()
		ivalue = o.items[iname]
		emit_col(DI + get_tab_char(1))
		emit_col(iname)
		if ivalue.value:
			emit_col(' = ' + str(ivalue.value))
		if cnt < l:
			emit_col(',')
		if ivalue.comment:
			emit_col_tail_comment(ivalue.comment, 4)
		emit_row_end()
		cnt += 1
	emit_table_end()
	emit(DI + '};')
def emit_public_members():
	keys = members.keys()
	keys.sort(key = lambda name: members[name].__instance_id__)
	l = len(keys)
	cnt = 0
	for key in keys:
		member = members[key]
		t = type(member).__name__
		if t == 'Enum':
			if cnt < l:
				emit('\\n')
			emit_enum(key, DI + get_tab_char(1), member)
			cnt += 1

def get_cpp_type_name(t):
	if t == 'Int32':
		return 'livscm::int32'
	elif t == 'SharedPointer':
		return 'livscm::SharedPtr'
	else:
		return t

def get_cpp_value(v):
	t = type(v).__name__
	r = ''
	if t == 'SharedPointer':
		r = get_cpp_type_name(t) + '('
		if v.value:
			t = t + get_cpp_type_name(t)
		r += ')'
	elif t == 'AddressOf':
		r = '&' + str(v.object)
	elif t == 'Int32':
		r = str(v.value)
	else:
		r = str(v)
	return r

def emit_private_members():
	keys = members.keys()
	keys.sort(key = lambda name: members[name].__instance_id__)
	l = len(keys)
	if l > 0:
		emit('\\n')
	cnt = 0
	emit_table_start()
	for key in keys:
		o = members[key]
		t = type(o).__name__
		if t != 'Enum':
			emit_row_start()
			is_static = hasattr(o, 'static') and o.static
			is_const = hasattr(o, 'const') and o.const
			has_comment = hasattr(o, 'comment') and o.comment
			emit_col(DI + get_tab_char(1))
			if is_static:
				emit_col('static ')
			if is_const:
				emit_col('const ')
			emit_col(get_cpp_type_name(t) + ' ', 3)
			emit_col(key)
			if is_static and is_const:
				emit_col(' = ' + get_cpp_value(o))
			emit_col(';')
			if has_comment:
				emit_col_tail_comment(o.comment, 7)
			cnt += 1
			emit_row_end()
	emit_table_end()
}$"""

class_template = """${DI}$class ${emit_class_name(); emit_derivation()}$
${DI}${
${DI}$public:${emit_public_members()}$
${DI}$protected:
${DI}$private:${emit_private_members()}$
${DI}$};"""

variable_template = """${DI}$${if const: emit('const ')}$${type}$ ${name}$ = ${value}$;"""

namespace_start_template = """${DI}$namespace ${namespace_name}$
${DI}${"""

namespace_end_template = """${DI}$} // end of namespace '${namespace_name}$'
"""

enum_template = """${emit_enum(name, DI, object)}$"""

def get_tab_char(count):
	if setting.tab_to_space:
		return ' ' * setting.tab_size * count
	else:
		return '\t' * count

def emit_tail_comment(DI, comment):
	lines = comment.split('\n')
	cnt = 0
	for line in lines:
		if cnt == 0:
			print(' // ' + line)
		else:
			print(DI + '// ' + line)
		cnt += 1

def generate(setting_, module, symbols):
	global setting
	setting = setting_
	mod_name = module.__name__
	mod_tmpl = utils.Templite(module_template)
	class_tmpl = utils.Templite(util_template + class_template)
	var_tmpl = utils.Templite(variable_template)
	ns_start_tmpl = utils.Templite(namespace_start_template)
	ns_end_tmpl = utils.Templite(namespace_end_template)
	enum_tmpl = utils.Templite(util_template + enum_template)

	namespaces = list(setting.default_namespaces)
	if setting.module_namespace:
		namespaces.append(mod_name)

	# Dump header
	print(mod_tmpl.render(module = mod_name))

	# Dump namespace
	ns_indent = 0
	for ns in namespaces:
		print( ns_start_tmpl.render(namespace_name = ns, DI = get_tab_char(ns_indent)) )
		ns_indent += 1
	default_indent = get_tab_char(len(namespaces))

	# Dump symbols
	for symbol in symbols:
		o = module.__dict__[symbol]
		t = type(o).__name__
		if t == 'type':
			members = {}
			if issubclass(o, lst.Base):
				for mnm in o.__dict__.keys():
					mo = o.__dict__[mnm]
					mt = type(mo).__name__
					if hasattr(mt, '__class__') and issubclass(mo.__class__, lst.Base):
						members[mnm] = mo
				print(class_tmpl.render(
					setting = setting,
					DI = default_indent,
					module = mod_name,
					name = symbol,
					bases = o.__bases__,
					members = members
					))
		elif t == 'Int32':
			s = var_tmpl.render(
				setting = setting,
				DI = default_indent,
				type = 'livscm::int32',
				name = symbol,
				value = o.value,
				const = o.const
			)
			sys.stdout.write(s)
			if o.comment:
				emit_tail_comment(' ' * (len(s) + 1), o.comment)
			else:
				print('')
		elif t == 'Enum':
			print(enum_tmpl.render(
				setting = setting,
				DI = default_indent,
				name = symbol,
				object = o,
			))
		else:
			print(o, t)

	# Namespace end
	namespaces.reverse()
	for ns in namespaces:
		ns_indent -= 1
		print( ns_end_tmpl.render(namespace_name = ns, DI = get_tab_char(ns_indent)) )

